/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
 * secPaver is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

/*
Package sehandle implements system policy context handle
*/
package sehandle

import (
	"fmt"
	"gitee.com/openeuler/secpaver/engine/selinux/pkg/libselinux"
	"gitee.com/openeuler/secpaver/engine/selinux/pkg/libsepol"
	"gitee.com/openeuler/secpaver/engine/selinux/pkg/secontext"
	"github.com/pkg/errors"
	"sort"
)

type Handle struct {
	fileContexts []*secontext.FileContext
	portContexts []*secontext.PortContext

	typeInfos []*libsepol.TypeInfo

	users []string
	roles []string
}

// HandleCreate creates a SELinux file context handel
func HandleCreate() (*Handle, error) {
	h := &Handle{}

	if err := h.readPolicyDb(); err != nil {
		return nil, err
	}

	if err := h.readFileContexts(); err != nil {
		return nil, err
	}

	return h, nil
}

func (h *Handle) readFileContexts() error {
	var ctxList []*secontext.FileContext
	paths := []string{
		libselinux.FileContextPath(),
		libselinux.FileContextHomedirPath(),
		libselinux.FileContextLocalPath(),
	}

	for _, path := range paths {
		l, err := secontext.ParseFileContextsFromFile(path)
		if err != nil {
			return fmt.Errorf("fail to parse %s", path)
		}

		ctxList = append(ctxList, l...)
	}

	sort.Slice(ctxList, func(i, j int) bool {
		return secontext.FileContextCompare(ctxList[i], ctxList[j])
	})

	h.fileContexts = ctxList

	return nil
}

func (h *Handle) readPolicyDb() error {
	handle, err := libsepol.HandleCreate()
	if err != nil {
		return err
	}
	defer libsepol.HandleDestroy(handle)

	spf, err := libsepol.PolicyFileCreate()
	if err != nil {
		return err
	}
	defer libsepol.PolicyFileFree(spf)

	policyFile := libselinux.CurrentPolicyPath()
	if policyFile == "" {
		return fmt.Errorf("fail to get current policy path, please check policy file")
	}

	fp, err := libsepol.Fopen(policyFile)
	if err != nil {
		return err
	}
	defer libsepol.Fclose(fp)

	libsepol.PolicyFileSetFp(spf, fp)
	libsepol.PolicyFileSetHandle(spf, handle)

	db, err := libsepol.PolicydbCreate()
	if err != nil {
		return err
	}
	defer libsepol.PolicydbFree(db)

	if err := libsepol.PolicydbRead(db, spf); err != nil {
		return err
	}

	if h.users, err = libsepol.GetAllUsers(db); err != nil {
		return err
	}

	if h.roles, err = libsepol.GetAllRoles(db); err != nil {
		return err
	}

	if h.typeInfos, err = libsepol.GetAllTypesAndAttrs(db); err != nil {
		return err
	}

	ports, err := libsepol.GetAllPorts(db)
	if err != nil {
		return err
	}

	if err := h.readPortContexts(ports); err != nil {
		return errors.Wrap(err, "fail to parse port context from db record")
	}

	return nil
}
